package org.gauravpandey.logictest;

/**
 * 
 * Programming Logic Test #2 
 * 
 * Using any common programming language, program a
 * quick and straightforward answer to the following question (e.g. C++). A
 * regular expression or other pattern matching package should not be used.
 * Presented with a character stream that is infinite, output a "D" if the
 * sequence "ddd" is located, precisely. Alternatively, if the characters "ded"
 * are located, output an "E". You should not output both a "D" and an "E" for
 * the same data when processed.
 * 
 * For example: 
 * 1. The following stream of characters ddededddeddd would output: EDD 
 * 2. The following stream of characters dddededdddeedede would output: DEDE
 * 
 * 
 * @author Gaurav Pandey
 * 
 * Programming Logic:
 * 
 * Queue (FIFO) is used to manage the sliding window which is of length 3 and
 * would be used to perform lookup for pattern matching operation ("ddd",
 * "ded"). The enqueue and dequeue operations are of use for us in the case of a
 * Character Stream "eeddde" as follows: 
 * Queue content: e   (Enqueue e) 
 * Queue content: ee  (Enqueue e) 
 * Queue content: eed (Enqueue d) 
 * Queue content: edd (Dequeue e and Enqueue d) 
 * Queue content: ddd (Dequeue e and Enqueue d) {MATCH-Output: D}
 * Queue content: dde (Dequeue d and Enqueue e) 
 * Queue content: de  (Dequeue d) 
 * Queue content: e   (Dequeue d)
 * 
 * 
 * Execution Steps: (Targetting Java 1.5)
 * ->In Windows Environment
 * Ater unzipping the 'LogicTest.zip' file, inside there will be a file
 * named 'make-windows.txt'. Rename the file using Windows Command Prompt
 * to 'make-windows.bat'. Execute the 'make-windows.bat' file by double-
 * clicking it or running from the command prompt itself by typing 'make'.
 *
 * ->In Unix and Linux Environment
 * After unzipping the 'LogicTest.zip' file, inside there will be a file
 * named 'Makefile'. Just type 'make' at the command prompt to begin
 * execution. Once execution has been achieved successfully, type 
 * 'make clean' to perform cleanup of existing binary class files.
 *
 * 
 * Assumption: 
 * 
 * -> Based on the sample data provided, I have assumed the character
 * stream to comprise of "d" and "e" only.
 * -> A stream has been assumed to be a continuous source of data. In my program,
 * there are 2 implementations.
 * 
 * 
 * Explanation:
 * 
 * In Case 1, I have decided to randomly choose either "d" or "e" over a while(true)
 * loop which is executing infinite number of times and it is purely
 * pseudo-random in nature by taking into account the Randomness associated with
 * the 'Random' Class present in Java API. This is my version of a Character Stream
 * of infinite length. This approach was followed as I cannot hold an infinite character
 * length stream in some file. I could have a remote network sending me random
 * characters. The code can easily be extended to that idea as well.
 * 
 * In Case 2, I chose to test my program by working on a fixed set of character
 * stream saved in some sample file. The user can either specify his own file or
 * test using the default sample file provided.  
 * 
 * In Case 3, User can enter his own character stream and test the existence of patterns
 * "ddd" and "ded" over it.  
 */

import static java.lang.System.out;
import static java.lang.System.err;
import static java.lang.Integer.parseInt;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;

public class LogicTest {
	private static char[] chArr = { 'd', 'e' };

	private static String queueddd = "[d, d, d]", queueded = "[d, e, d]";

	public static void main(String[] args) {
		/* Data structure used to manage the sliding window */
		Queue<Character> queue = new LinkedList<Character>();

		String stream = null;
		int choice = -1;

		out.print("\n\n");
		out.println("********************************************************");
		out.println("Welcome to the solution of Programming Logic 2");
		out.println("Program detects patterns \"ddd\" and \"ded\" in a stream");
		out.println("and outputs \"D\" or \"E\" accordingly.");
		out.println("********************************************************");
		out.println("Press 1 to work on infinite-length character stream");
		out.println("Press 2 to work on finite-length char stream from a file");
		out.println("Press 3 to manually feed a character stream dynamically");
		out.println("Press 4 to exit");
		out.println("********************************************************");
		out.println("For selection 1, press CTRL+C to break the infinite loop");
		out.print("Enter your choice: ");

		/* Managing the User Input and exception handling */
		try {
			choice = parseInt((new BufferedReader(new InputStreamReader(
					System.in))).readLine());
		} catch (IOException ioe) {
			err.println("IOException occured. Please retry.");
		} catch (NumberFormatException nfe) {
			choice = -1;
		} catch (Exception e) {
			err.println("General exception occurred. Please retry.");
		}

		if (choice < 0) {
			err.println("Use positive numeric values. Try again.");
			System.exit(0);
		}

		/* Code to interpret the Menu Interaction */
		switch (choice) {
		/* Implementing Infinite looping character stream generator */
		case 1:
			infiniteDataPatternDetector(queue);
			/*
			 * Implemented using a file (Good for testing purpose - fixed
			 * length)
			 */
		case 2:
			/*
			 * User has the option to specify the filename which has input data
			 */
			out.print("\nEnter a filename to test ");
			out.print("(or press ENTER to test with default file): ");
			try {
				stream = (new BufferedReader(new InputStreamReader(System.in)))
						.readLine();
			} catch (IOException ioe) {
				err.println("IOException occured. Please retry.");
			} catch (Exception e) {
				err.println("Exception occured. Please retry.");
			}

			/*
			 * A default file with sample data is chosen if no name is provided
			 * by the user
			 */
			if (stream.length() == 0) {
				stream = "dddded_list.txt";
			}

			/* Lookup is performed whether the specified file does exist or not */
			File f = new File(stream);
			out.println(f + (f.exists() ? " is found " : " is missing "));

			/*
			 * File is read character by character and same programming logic is
			 * implemented as suggested in Case 1
			 */
			if (f.exists()) {
				filePatternDetector(queue, stream);
			}
			break;
		/* User wishes to enter data dynamically at runtime */
		case 3:
			/*
			 * User has the option to specify the character stream manually
			 */
			out.print("\nEnter a stream to detect pattern upon: ");
			try {
				stream = (new BufferedReader(new InputStreamReader(System.in)))
						.readLine();
			} catch (IOException ioe) {
				err.println("IOException occured. Please retry.");
			} catch (Exception e) {
				err.println("Exception occured. Please retry.");
			}

			userDataPatternDetector(queue, stream);
			break;
		/* User wishes to exit the Menu */
		case 4:
			out.println("\nBye.");
			break;
		/* User has provided an invalid argument as input */
		default:
			out.println("Invalid entry. Please retry.");
			break;
		}
	}

	/* Implementation of the functionality for Case 1 */
	private static void infiniteDataPatternDetector(Queue<Character> queue) {
		/* Used in the random generation of "d" and "e" character stream */
		Random r = new Random();

		while (true) {
			/*
			 * A random value will be chosen between (0,1) which would be the
			 * index of the chArr[]. Appropriately, "d" or "e" is chosen and
			 * wrapped by the Character class and added to the Queue
			 */
			queue.add(new Character(LogicTest.chArr[r.nextInt(2)]));

			/*
			 * Once the queue has reached the size of the sliding window,
			 * pattern matching will commence for presence of "ddd" or "ded". If
			 * suitable pattern is found, appropriate output is generated.
			 */
			if (queue.size() == 3) {
				if (queue.toString().equalsIgnoreCase(queueddd)) {
					out.println("D");

					/*
					 * No pattern matching performed over past entries. Thus,
					 * queue is cleared up.
					 */
					queue.clear();
				} else if (queue.toString().equalsIgnoreCase(queueded)) {
					out.println("E");

					/*
					 * No pattern matching performed over past entries. Thus,
					 * queue is cleared up.
					 */
					queue.clear();
				} else {
					/* Dequeue operation */
					queue.remove();
				}
			}
		}
	}

	/* Implementation of the functionality for Case 2 */
	private static void filePatternDetector(Queue<Character> queue,
			String fileName) {
		int in = 0;

		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(
					new FileInputStream(fileName)));

			while ((in = br.read()) != -1) {
				queue.add(new Character((char) in));
				if (queue.size() == 3) {
					if (queue.toString().equalsIgnoreCase(queueddd)) {
						out.println("D");
						queue.clear();
					} else if (queue.toString().equalsIgnoreCase(queueded)) {
						out.println("E");
						queue.clear();
					} else {
						queue.remove();
					}
				}
			}
		} catch (Exception e) {
			err.println("Exception occurred");
		}
	}

	/* Implementation of functionality for Case 3 */
	private static void userDataPatternDetector(Queue<Character> queue,
			String userData) {
		char[] userChar = userData.toCharArray();
		for (int i = 0; i < userChar.length; i++) {
			queue.add(new Character(userChar[i]));
			if (queue.size() == 3) {
				if (queue.toString().equalsIgnoreCase(queueddd)) {
					out.println("D");
					queue.clear();
				} else if (queue.toString().equalsIgnoreCase(queueded)) {
					out.println("E");
					queue.clear();
				} else {
					queue.remove();
				}
			}
		}
	}
}
